home *** CD-ROM | disk | FTP | other *** search
- /* fmutl.c - flash memory utility routines */
-
- /***
- Created 1993-4. Copyright (C)
- Management Graphics, Inc., Minneapolis, Minnesota
- All Rights Reserved.
-
- Confidential information - For limited distribution
- to authorized persons within MGI only. This software
- and data are protected as an unpublished work under
- the U.S. Copyright Act of 1976.
-
-
- -------------- Module Description: --------------
-
- This module contains utility routines which interface to the
- Flash Memory devices. These replace EPROMs for firmware.
-
- The primary external routines are:
- fm_status() - resets chips and returns FMINFO structure data;
- fm_write() - writes data to the flash memory.
- Data in flash is read like normal memory.
-
- IMPORTANT: The E_ITER and W_ITER constants are implementation-dependent.
- They control time-out check loops, and must be maintained. Re-measure the
- correct values for these constants if any of the following change:
- - the code in the loops controlled by those constants;
- - the compiler or optimization switches;
- - CPU clock speed;
- - memory access time;
- - anything else which may change the execution speed
- of the instructions in those loops.
- Note that these constants set worst-case limits, so things like
- interrupt handlers can be ignored, since they will only minimally
- lengthen the time-out checks.
-
-
- GENERAL NOTES:
-
- This was originally coded for AMD Am29F010 and Am29F040 devices.
- See also the Advanced Micro Devices (AMD) spec sheets (AMD publication
- numbers 16736 and 17113, respectively).
-
- Note that our hardware arranges that word accesses are handled as two
- parallel byte accesses to the devices (simultaneous odd- and even-byte
- device accessing). This is why:
- - all read and write accesses to Flash Memory
- are made as 16-bit words (unsigned shorts);
- - command codes are word wide (repeating
- the command bytes twice); and why
- - some length and address values are shifted
- by one (byte- to word-address conversion).
- This has significant impact for some algorithms; see the Timeout
- Detections Notes, below.
-
-
- TIMING NOTES:
-
- Timing tests on 2-Mar-94 with not-quite-new chips indicate the following
- (random words erased then random words written):
- Erase Write Bytes Bits Erase Write
- Device all all Total (per chip) sector sector Total
- ======== ----- ----- ----- ----- ---- ------ ------ -----
- Am29F010 4.2 4.9 9.1 128K 1M 0.9 0.8 1.7
- Am29F040 8.7 14.1 22.8 512K 4M 2.2 1.9 4.1
-
- The spec sheets imply that erase and write times may significantly
- increase with device age/use. Erases may take x10 to x20 (or more)
- as long; writing may take x60 to x4000 (yes, 4K) as long.
-
- It was observed that erasing 1's takes longer (up to x4)
- than erasing 0's (i.e. erasing previously written values).
-
- It was also observed that writing 0's takes longer (up to x3)
- than writing 1's (presumably because erase leaves all 1's).
-
- 7-Apr-94: the fm_cpy() routine was made significantly (>11%) faster; the
- table above does *not* reflect this improvement.
-
-
-
- CODING NOTES AND WARNINGS:
-
- It is assumed that the base address of the Flash Memory is an even
- multiple of the total size of the Flash memory. In other words, the
- address of the first location in Flash is assumed to have all possible
- lower bits zero. This allows the code to mask off the lower bits to
- obtain the Flash base address. It is also assumed that the number of
- addressable bytes are a power of 2.
-
- Note also that address 0x00000000 can be a valid address. It doesn't
- make sense in 'C', but it makes sense in hardware. Looping structures
- like: for (ptr1 = ptr0; ptr1 >= ptr2; ++ptr1) {...}
- tend to be dangerous when ptr2 is allowed to be zero. Safe programming
- will also allow for pointers high in memory (e.g: 0xFFFFFFFE). These
- considerations require a more defensive than usual approach in the
- code. Examples of safer approaches: loops not based on pointer
- comparisons, and normalizing pointers before looping.
-
-
- NOTES ABOUT TIMEOUT DETECTION:
-
- A timeout happens when an erase or byte-write operation takes "too long"
- (see the Timing Notes section above). The chips have built-in timers to
- detect this, but the code to take advantage of this feature ends up
- being a little cryptic. Here is a brief description.
-
- When a chip times out, bit 5 (DQ5) is raised in the value read back.
- It sounds simple. However, this condition is only valid when DQ7 is
- in the NOT-DATA-POLLING state (the inverse of the expected value).
- I.e, DQ5 can only be trusted if DQ7 does not yet indicate completion of
- the erase or write. Additionally, since two chips service each word
- (odd & even byte), each byte needs to be checked independently. This
- is because one chip may timeout while the other is ok. To further
- complicate things, DQ5 and DQ7 may change simultaneously; this requires
- a second check to confirm that the timeout value read isn't actually
- the chip data lines in transition to valid data.
-
- The timeout detection code here first constructs a to_val temporary,
- which, for each byte, has DQ5 set, has DQ7 in the NOT-DATA-POLLING state,
- and the other 6 bits are zero. To test for timeout, the DQ5 and DQ7 bits
- are first masked out of the current value, and then xor-ed with to_val. If
- the result for the odd *or* even byte is zero, then a timeout has occurred.
-
- For speed, the code only checks for timeout every so often. This allows
- the inner-loop to be as fast as possible, which is very important to the
- fm_cpy() routine since it writes each byte this way. See also the data
- sheet for an explanation of the recommended "!Data Polling Algorithm"
- (which this is based on).
-
- When interpreting the timeout error codes, keep in mind DQ6, the
- Toggle Bit (which is not used in the algorithms here).
-
- ***/
-
-
- #include <string.h>
- #include "fmutl.h"
-
-
- /** all these magic numbers are from the AMD spec sheets: **/
-
- /* address and data for command writes: */
- #define FM_ADDR1 0x5555 /* these are for the first "magic */
- #define FM_DATA1 0xAAAA /* write" of a command preamble */
- #define FM_ADDR2 0x2AAA /* these are for the second "magic */
- #define FM_DATA2 0x5555 /* write" of a command preamble */
-
- /* command codes: */
- #define FMCMD_RESET 0xF0F0 /* "Read/Reset Command" */
- #define FMCMD_AUTOSEL 0x9090 /* "Autoselect Command" */
- #define FMCMD_WRITE 0xA0A0 /* "Byte Program Command" */
- #define FMCMD_ERASE 0x8080 /* "Chip or Sector Erase Command" */
-
- /* use this to skip last step of a command preamble [see fm_cmd()]: */
- #define FMCMD_NONE 0x0000 /* no command */
-
- /* secondary command codes for erase commands: */
- #define FMCMD2_SECTOR 0x3030 /* "Sector Erase" */
- #define FMCMD2_CHIP 0x1010 /* "Chip Erase" */
-
- /* miscellaneous other flash memory constants: */
- #define FM_PROT_SECT_MASK 0x0101 /* protected sector indicator bit(s) */
- #define FM_ERASE_VAL 0xFFFF /* value of an erased location */
- #define FM_TIMEOUT 0x2020 /* DQ5 - goes hi if device times out */
- #define FM_NDATA_POLL 0x8080 /* DQ7 - inverse of final data */
-
- /* special address offsets for Autoselect command reads: */
- #define FM_AO_MANUF 0 /* for manufacturer code readback */
- #define FM_AO_DEVID 1 /* for device ID readback */
- #define FM_AO_PROT_SECT 2 /* for protected sector readbacks */
-
-
- FMERR fm_err; /* global Flash error structure */
-
- static char *ErrMsgs[] = { /** fm_err.code and fm_err.pMsg values: **/
- "x?? Unknown err", /* 0: unused */
- "Bad devID codes", /* 1: unknown/mismatched/missing/bad chips */
- "Erase fail-safe", /* 2: sector erase fail-safe triggered */
- "Erase timed out", /* 3: sector erase command timed out */
- "Write fail-safe", /* 4: byte write fail-safe triggered */
- "Write timed out", /* 5: byte write programming timed out */
- "fm_write params", /* 6: address or length parameter was odd */
- "fm_write overfl", /* 7: attempt to write past end of flash */
- }; /** see also ErrMsg[] in fm_error() **/
-
-
- static FMINFO DevTable[] = { /* known device data table: */
- /* MfrID DevID EFS WFS NS SectSiz*2 TotSize*2 NP FP DevNameString */
- 0x0101, 0x2020, 20, 90, 8, 0x04000*2, 0x20000*2, 0, 0, "AMD Am29F010",
- 0x0101, 0xA4A4, 40, 90, 8, 0x10000*2, 0x80000*2, 0, 0, "AMD Am29F040",
- };
-
-
- /* the routines in this module need to share this data: */
- static unsigned volatile short *pBase; /* ptr to the base address of Flash */
- static FMINFO FMInfo; /* info specific to these chips */
-
-
- /* make it easy to give flashtst.c access to some internal routines: */
- #ifndef STATIC
- #define STATIC static
- #endif
-
- #define DUMP_FE 0 /* non-zero to dump function params on entry */
-
-
-
- /*****************************************************************************
- Set values in the global error structure.
- *****************************************************************************/
-
- void fm_error(addr, exp, act, errcode)
- unsigned short *addr, exp, act;
- int errcode; /* only the low byte is significant */
- {
- static char ErrMsg[20];
- static char Hex[] = "0123456789ABCDEF";
-
- /* fill in the easy stuff: */
- fm_err.addr = addr;
- fm_err.exp = exp;
- fm_err.act = act;
- fm_err.code = (unsigned char) errcode;
-
- /* either use a built-in message, or construct one using errcode: */
- if (1 <= errcode && errcode < ARRAY_LEN(ErrMsgs))
- fm_err.pMsg = ErrMsgs[errcode];
- else
- { strncpy(ErrMsg, ErrMsgs[0], sizeof(ErrMsg));
- ErrMsg[1] = Hex[fm_err.code / 16];
- ErrMsg[2] = Hex[fm_err.code % 16];
- fm_err.pMsg = ErrMsg;
- }
- }
-
-
-
- /*****************************************************************************
- Send unlock commands to the chips. This magic permutation of writes takes
- the chips out of normal read-only mode, and puts them into the indicated
- command mode (see the FMCMD_ constants). This code is replicated in the
- fm_cpy() routine for performance reasons.
- *****************************************************************************/
-
- static void fm_cmd(cmd)
- unsigned short cmd; /* command code = an FMCMD_ constant */
- {
- /*?* printf("fm_cmd(%04x), pBase = %08x\n", cmd, pBase); /**/
-
- /* write first magic value to first magic location: */
- *(pBase + FM_ADDR1) = FM_DATA1;
-
- /* write second magic value to second magic location: */
- *(pBase + FM_ADDR2) = FM_DATA2;
-
- /* if desired, write a command code to the final magic location: */
- if (cmd != FMCMD_NONE)
- *(pBase + FM_ADDR1) = cmd;
- }
-
-
-
- /*****************************************************************************
- Reset the devices. Two resets are performed because the first may not
- be accepted if the devices were left in a strange state (e.g. if a prior
- command sequence was interrupted before completion). The second is sure
- to be heard by sane devices.
- *****************************************************************************/
-
- static void fm_reset()
- {
- fm_cmd(FMCMD_RESET);
- fm_cmd(FMCMD_RESET);
- }
-
-
-
- /*****************************************************************************
- Provides information about protected sectors in the chips. FMInfo and pBase
- are assumed to be initialized. Leaves the chips in AutoSelect mode.
- *****************************************************************************/
-
- static void fm_protected(pFirstProt, pNumProt)
- int *pFirstProt, *pNumProt; /* see the FMINFO struct for details */
- {
- unsigned volatile short *ptr; /* ptr into flash space */
- unsigned short val; /* value read back */
- int sect; /* iterates thru the sectors */
-
- /* init our pointer to the magic offset in the last sector: */
- ptr = pBase + ((FMInfo.TotalSize >> 1) + FM_AO_PROT_SECT);
- ptr -= FMInfo.SectorSize >> 1;
-
- /* command the chip to read back the protected sector data: */
- fm_cmd(FMCMD_AUTOSEL);
-
- /* initialize these to mean "no protected sectors found": */
- *pNumProt = 0;
- *pFirstProt = -1;
-
- /* loop checking each sector: */
- for (sect = FMInfo.NumSect_; --sect >= 0; )
- {
- /* bit set indicates sector is protected: */
- val = *ptr & FM_PROT_SECT_MASK;
- if (val)
- { /* if both devices don't agree, error out: */
- if (val != FM_PROT_SECT_MASK)
- { *pNumProt = -2;
- return;
- }
-
- /* if protection is non-continuous, error out: */
- if (*pFirstProt != -1 && *pFirstProt != sect + 1)
- { *pNumProt = -1;
- return;
- }
-
- /* set index to this sector and bump the count: */
- *pFirstProt = sect;
- ++*pNumProt;
- }
-
- /* bump pointer to the magic location in the next sector: */
- ptr -= FMInfo.SectorSize >> 1;
- }
- }
-
-
-
- /*****************************************************************************
- Resets and returns data about the flash chips. Fills in our local
- FMInfo structure, and optionally fills in an FMINFO structure for the
- caller. Leaves the chip(s) in the normal ROM state. Zero is returned
- on error and fm_err contains failure data.
- *****************************************************************************/
-
- fm_status(pbase, pFMInfo)
- unsigned short *pbase; /* must point to the *base* of the Flash memory */
- FMINFO *pFMInfo; /* may be NULL */
- {
- unsigned short mfr, dev; /* IDs read back from devices */
- FMINFO *pDev; /* traverses the DevTable[] */
-
- /* share this with other routines in this module: */
- pBase = pbase;
-
- /* clear out error structure: */
- memset(&fm_err, 0, sizeof(fm_err));
-
- /* reset the chip: */
- fm_reset();
-
- /* send down command to read back the embedded data: */
- fm_cmd(FMCMD_AUTOSEL);
-
- /* read back the codes: */
- mfr = *(pBase + FM_AO_MANUF);
- dev = *(pBase + FM_AO_DEVID);
-
- /* check for known devices: */
- pDev = DevTable + ARRAY_LEN(DevTable);
- while (--pDev >= DevTable)
- if (pDev->MfrID_ == mfr && pDev->DevID_ == dev)
- { memcpy(&FMInfo, pDev, sizeof(FMInfo));
- break;
- }
-
- /* if not found in our table, return error: */
- if (pDev < DevTable)
- { /* (could also be mismatched, missing, or fried devices) */
- fm_reset();
- fm_error(pBase, mfr, dev, 1);
- return 0;
- }
-
- /* go get & fill in protected sector information: */
- fm_protected(&FMInfo.FirstProt, &FMInfo.NumProt);
-
- /* fill in structure for caller if they want us to: */
- if (pFMInfo)
- memcpy(pFMInfo, &FMInfo, sizeof(FMINFO));
-
- /* exit readback mode and we're done: */
- fm_reset();
- return 1;
- }
-
-
-
- /*****************************************************************************
- Handle a time-out or fail-safe error. The expected value is checked with
- the actual value to determine if a time-out happened. fm_error is called
- with errcode for a fail-safe error, or with errcode+1 for a time-out error.
- *****************************************************************************/
-
- static void fm_tofs_error(addr, exp, act, errcode)
- unsigned short *addr, exp, act;
- int errcode; /* fail-safe error=errcode; time-out error=errcode+1 */
- {
- unsigned short to_val; /* value used to check for time-out */
- unsigned short tmp; /* time-out check temporary */
-
- /* this value has timeout & data-poll bits set to mean time-out: */
- to_val = (~exp & FM_NDATA_POLL) | FM_TIMEOUT;
-
- /* mask out the interesting bits and xor them: */
- tmp = (act & (FM_TIMEOUT | FM_NDATA_POLL)) ^ to_val;
-
- /* both bits clear in a byte means that chip timed-out: */
- if (!(tmp & 0x00ff) || !(tmp & 0xff00))
- ++errcode;
-
- /* timeout error or fail-safe error handling: */
- fm_error(addr, exp, act, errcode);
-
- /* leave chips usable */
- fm_reset();
- }
-
-
-
- /*****************************************************************************
- Erase by sectors. The sector(s) which are overlapped by the indicated
- flash address memory range are erased. Returns non-zero on success;
- otherwise zero is returned and fm_err contains failure data. Assumes
- FMInfo and pBase are initialized.
- *****************************************************************************/
-
- #define E_FS_ITER 240000 /* number of fail-safe loop iterations per sec. */
- /** Re-measure this constant if the fail-safe loop is changed! **/
-
- STATIC fm_sector_erase(pLow, pHigh)
- volatile unsigned short *pLow; /* ptr somewhere into first sector to erase */
- volatile unsigned short *pHigh; /* ptr somewhere into last sector to erase */
- {
- size_t SectorAddrMask; /* converts ptr to offset within a sector */
- size_t SectOffset; /* offset into ending sector */
- unsigned long fail_safe; /* safety counter */
-
- #if DUMP_FE
- /*?*/ printf("fm_sector_erase(%08x %08x)\n", pLow, pHigh);
- #endif
- /* this masks off address bits indicating offsets into a sector: */
- SectorAddrMask = FMInfo.SectorSize - 1;
-
- /* set end pointer to the last location in it's sector: */
- SectOffset = ((size_t) pHigh) & SectorAddrMask;
- pHigh = pHigh - (SectOffset >> 1) + (FMInfo.SectorSize >> 1) - 1;
-
- /* send erase preamble sequence: */
- fm_cmd(FMCMD_ERASE);
-
- /* send sector erase preamble sequence: */
- fm_cmd(FMCMD_NONE);
-
- /* loop indicating which sectors to erase: */
- while (pLow <= pHigh)
- {
- /* tell chip to erase this sector: */
- *pLow = FMCMD2_SECTOR;
-
- /* bump pointer by one sector: */
- pLow += FMInfo.SectorSize >> 1;
- }
- #if 00
- /*?*/ DspWrite("ready\n "); DspNum(FMInfo.MaxErase_, 1, 0, 8);
- /*?*/ pHigh = 100; Delay(2000);
- /*?*/ DspWrite("go!\n "); VBeep(50);
- #endif
- /* loop checking for erase completion: */
- for (fail_safe = FMInfo.MaxErase_ * E_FS_ITER; --fail_safe; )
- if (*pHigh == FM_ERASE_VAL)
- return 1; /* done! */
- #if 00
- /*?*/ DspWrite("done\n "); VBeep(200); while (1) { } /**/
- #endif
- /* time-out and fail-safe error handling: */
- fm_tofs_error(pHigh, FM_ERASE_VAL, *pHigh, 2);
- return 0;
- }
-
-
-
- /*****************************************************************************
- Internal routine to copy data to Flash Memory. It is assumed that the
- destination area has been erased, and that bytes is an even number.
- Returns non-zero on success; zero is returned on error and fm_err contains
- failure data. FMInfo and pBase are assumed initialized.
- * * * NOTE: This routine has been hand optimized for performance. * * *
- *****************************************************************************/
-
- #define W_FS_ITER 350 /* number of fail-safe loop iterations per msec */
- /** Re-measure this constant if the fail-safe loop is changed! **/
-
- static fm_cpy(pDst, pSrc, bytes)
- register volatile unsigned short *pDst; /* where in Flash to copy data to */
- register unsigned short *pSrc; /* where to copy data from */
- register size_t bytes; /* # bytes to copy -- must be even */
- {
- register volatile unsigned short *ptr1; /* fast FM_ADDR1 ptr */
- register volatile unsigned short *ptr2; /* fast FM_ADDR2 ptr */
- register unsigned short fmd1, fmd2; /* fast FM_DATA values */
- register unsigned short val; /* value read in verify loop */
- register unsigned short fail_safe; /* safety counter */
-
- #if DUMP_FE
- /*?*/ printf("fm_cpy(%08x %08x %06x)\n", pDst, pSrc, bytes);
- #endif
- /* put these in registers for speed: */
- ptr1 = pBase + FM_ADDR1;
- ptr2 = pBase + FM_ADDR2;
- fmd1 = FM_DATA1;
- fmd2 = FM_DATA2;
-
- /* loop writing and verifying a word at a time: */
- while (bytes)
- {
- /* this duplicates a call to fm_cmd(), for speed: */
- *ptr1 = fmd1;
- *ptr2 = fmd2;
- *ptr1 = FMCMD_WRITE;
-
- /* write two bytes: */
- *pDst = *pSrc;
-
- /* do this here - a bit of parallel processing: */
- bytes -= 2;
- val = *pSrc;
- #if 000
- /*?>^*/ register unsigned short j;
- /*?*/ DspWrite("READY\n "); DspNum(FMInfo.MaxWrite_, 1, 0, 8);
- /*?*/ pDst = 100; Delay(2000);
- /*?*/ DspWrite("GO!\n "); VBeep(50);
- /*?*/ for (j = 1000; --j; )
- #endif
- /* loop checking for write completion: */
- for (fail_safe = FMInfo.MaxWrite_ * W_FS_ITER; --fail_safe; )
- if (*pDst == val)
- break; /* done! */
- #if 000
- /*?*/ DspWrite("DONE\n "); VBeep(200); while (1) { } /**/
- #endif
- /* if loop was exhausted, go handle the error: */
- if (fail_safe == 0)
- { fm_tofs_error(pDst, val, *pDst, 4);
- return 0;
- }
-
- /* move to the next location and loop back: */
- ++pSrc;
- ++pDst;
- }
-
- /* return happy: */
- return 1;
- }
-
-
-
- /*****************************************************************************
- Write data to the Flash Memory. Returns non-zero on success; on error
- zero is returned and fm_err contains failure data. The pDst and length
- parameters should be word aligned.
-
- The pScrBuf parameter determines the fate of words erased in a sector but
- not written with new data:
- if (pScrBuf == NULL), then writes to partial sectors leave the
- unwritten words of those sectors erased & unwritten.
- if (pScrBuf != NULL), then *pScrBuf is assumed to point to at least
- FMINFO.SectorSize bytes, and unwritten (but erased) words
- in partially written sectors will be saved (in the scratch
- buffer) and re-written, maintaining their prior values.
- *****************************************************************************/
-
- fm_write(pbase, pDst, pSrc, length, pScrBuf)
- unsigned short *pbase; /* ptr to the base of the Flash memory */
- unsigned short *pDst; /* where to write in Flash Memory space */
- unsigned short *pSrc; /* bytes from here are copied to Flash */
- size_t length; /* copy this many bytes from pSrc to pDst */
- unsigned short *pScrBuf; /* scratch buffer for partial sector writes */
- {
- size_t wri_len; /* # partial sector bytes of user data */
- size_t keep_before; /* # partial sector bytes prior to user data */
- size_t keep_after; /* # partial sector bytes after user data */
- size_t offset; /* word offsets for various uses */
- size_t SectorAddrMask; /* converts ptr to offset within a sector */
- unsigned short *pSector; /* ptr to even sector boundary */
- char errcode; /* non-zero indicates a parameter error */
-
- #if DUMP_FE
- printf("fm_write(%08x %08x %08x %08x %08x)\n", pbase, pDst, pSrc, length, pScrBuf);
- #endif
- /* share this with other routines in this module: */
- pBase = pbase;
-
- /* clear out error structure: */
- memset(&fm_err, 0, sizeof(fm_err));
-
- /* reset the chip and load FMInfo with info about it: */
- if (!fm_status(pBase, (FMINFO *) 0))
- return 0;
-
- /* check input parameters for word alignment and overflow: */
- errcode = 0;
- if (((size_t) pDst & 1) || (length & 1))
- errcode = 6;
- else if (pDst + (length >> 1) > pBase + (FMInfo.TotalSize >> 1))
- errcode = 7;
-
- /* these errors return length in pieces in the exp and act fields: */
- if (errcode)
- { fm_error(pDst, (unsigned short) length >> 8,
- (unsigned short) length & 0xFFFF, errcode);
- return 0;
- }
-
- /* this masks off address bits indicating offsets into a sector: */
- SectorAddrMask = FMInfo.SectorSize - 1;
-
- /* see if we are starting on an even sector boundary: */
- keep_before = ((size_t) pDst) & SectorAddrMask;
-
- /* if the first sector is a partial, do it separately: */
- if (keep_before)
- {
- /* get a pointer to the start of the sector: */
- pSector = pDst - (keep_before >> 1);
-
- /* compute # bytes in this sector being written by user: */
- wri_len = FMInfo.SectorSize - keep_before;
- if (length < wri_len)
- wri_len = length;
-
- /* compute # bytes to keep after area being written: */
- keep_after = FMInfo.SectorSize - keep_before - wri_len;
-
- /* read existing Flash sector into scratch buffer: */
- if (pScrBuf)
- memcpy(pScrBuf, pSector, FMInfo.SectorSize);
-
- /* erase the sector: */
- if (!fm_sector_erase(pDst, pDst))
- return 0;
-
- /* copy the user's data to Flash: */
- if (!fm_cpy(pDst, pSrc, wri_len))
- return 0;
-
- /* restore data in sector before user's data: */
- if (pScrBuf)
- if (!fm_cpy(pSector, pScrBuf, keep_before))
- return 0;
-
- /* restore data in sector after user's data: */
- if (pScrBuf && keep_after)
- { offset = (wri_len + keep_before) >> 1;
- if (!fm_cpy(pSector + offset,
- pScrBuf + offset, keep_after))
- return 0;
- }
-
- /* bump pointers: */
- pDst += (wri_len >> 1);
- pSrc += (wri_len >> 1);
- length -= wri_len;
-
- /* quit if done: */
- if (!length)
- return 1;
- }
-
- /* see if we are ending on an even sector boundary: */
- wri_len = length & SectorAddrMask;
-
- /* handle partial final sector: */
- if (wri_len)
- {
- /* get a pointer to the start of the final sector: */
- pSector = pDst + ((length - wri_len) >> 1);
-
- /* compute # bytes to keep after area being written: */
- keep_after = FMInfo.SectorSize - wri_len;
-
- /* read partial existing Flash sector into scratch buffer: */
- if (pScrBuf)
- memcpy(pScrBuf, pSector + (wri_len >> 1), keep_after);
- }
-
- /*?* VBeep(50); /**/
- /* erase the remaining sector(s): */
- if (!fm_sector_erase(pDst, pDst + (length >> 1) - 1))
- return 0;
- /*?* VBeep(50); Delay(200); VBeep(50); Delay(200); /**/
-
- /* copy the user's data to Flash: */
- if (!fm_cpy(pDst, pSrc, length))
- return 0;
- /*?* VBeep(50); Delay(200); VBeep(50); Delay(200); VBeep(50); /**/
-
- /* restore data in sector after user's data: */
- if (wri_len && pScrBuf && keep_after)
- { offset = wri_len >> 1;
- if (!fm_cpy(pSector + offset, pScrBuf, keep_after))
- return 0;
- }
-
- /* return happy: */
- return 1;
- }
-
-